﻿////////////////////////////////////////////////////////////////////////////////////
//
// Copyright (c) 2008 Piers Lawson
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of
// this software and associated documentation files (the "Software"), to deal in the
// Software without restriction, including without limitation the rights to use,
// copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
// Software, and to permit persons to whom the Software is furnished to do so,
// subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
// AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
////////////////////////////////////////////////////////////////////////////////////

using System;
using System.Collections.Generic;
using System.Globalization;
using System.Web;
using System.Web.Caching;
using System.Web.Compilation;
using System.Web.Configuration;
using System.Web.Mvc;
using System.Web.Script.Serialization;

namespace ResourcesOverMvc.Web.Mvc
{
    public class WebConfigJavaScriptConverterEngine
    {
        private readonly string _cacheKeyPrefixJsonConverter;

        internal static IJavaScriptConverterCache JavaScriptConverterCache { get; set; }

        public string WebConfigLocationFormat { get; set; }

        public WebConfigJavaScriptConverterEngine()
        {
            WebConfigLocationFormat = "~/Views/{0}";
 
            if (HttpContext.Current == null || HttpContext.Current.IsDebuggingEnabled)
            {
                JavaScriptConverterCache = new NullJavaScriptConverterCache();
            }
            else
            {
                JavaScriptConverterCache = new AspNetCacheJavaScriptConverterCache();
            }

            string className = GetType().FullName;
            _cacheKeyPrefixJsonConverter = className + ":JavaScriptConverter:";
        }

        public virtual JavaScriptConverterEngineResult FindJsonConverters(ControllerContext controllerContext)
        {
            if (controllerContext == null)
            {
                throw new ArgumentNullException("controllerContext");
            }

            string controllerName = controllerContext.RouteData.GetRequiredString("controller");

            // Fix to cache generation as outlined at http://forums.asp.net/p/1316297/2604641.aspx
            string cacheKey = _cacheKeyPrefixJsonConverter + controllerName;
            JavaScriptConverterEngineResult result = JavaScriptConverterCache.Get(cacheKey);

            // If the converters are not cached, we need to create a JavaScriptConverterEngineResult from scratch
            if (result == null)
            {
                // Load the appropriate Web.Config for the current controller.
                System.Configuration.Configuration configuration = WebConfigurationManager.OpenWebConfiguration(String.Format(CultureInfo.InvariantCulture, WebConfigLocationFormat, controllerName));

                // Get the JSON section.
                var jsonSection = (ScriptingJsonSerializationSection)configuration.GetSection("system.web.extensions/scripting/webServices/jsonSerialization");

                // Load the configuration details into the result object
                result = new JavaScriptConverterEngineResult(new List<JavaScriptConverter>())
                             {
                                 MaxJsonLength = jsonSection.MaxJsonLength,
                                 RecursionLimit = jsonSection.RecursionLimit
                             };

                foreach (Converter converterDefinition in jsonSection.Converters)
                {
                    Type converterType = BuildManager.GetType(converterDefinition.Type, false);

                    if (converterType == null)
                    {
                        result.Errors.Add("Unknown type found whilst loading JavaScriptConverters: " + converterDefinition.Type);
                    }
                    else
                    {
                        if (!typeof(JavaScriptConverter).IsAssignableFrom(converterType))
                        {
                            result.Errors.Add("Non-JavaScriptConverter type found whilst loading JavaScriptConverters: " + converterDefinition.Type);
                        }
                        else
                        {
                            result.Converters.Add((JavaScriptConverter)Activator.CreateInstance(converterType));
                        }
                    }
                }

                // Add the new result to the cache
                JavaScriptConverterCache.Set(cacheKey, result);
            }

            return result;
        }

        private class AspNetCacheJavaScriptConverterCache : IJavaScriptConverterCache
        {
            private readonly TimeSpan _slidingTimeout = new TimeSpan(0, 15, 0);

            public JavaScriptConverterEngineResult Get(string cacheKey)
            {
                return (JavaScriptConverterEngineResult)HttpContext.Current.Cache[cacheKey];
            }

            public void Set(string cacheKey, JavaScriptConverterEngineResult javaScriptConverters)
            {
                HttpContext.Current.Cache.Insert(cacheKey, javaScriptConverters, null, Cache.NoAbsoluteExpiration, _slidingTimeout);
            }
        }

        private class NullJavaScriptConverterCache : IJavaScriptConverterCache
        {
            public JavaScriptConverterEngineResult Get(string cacheKey)
            {
                return null;
            }

            public void Set(string cacheKey, JavaScriptConverterEngineResult javaScriptConverters)
            {
            }
        }

        public interface IJavaScriptConverterCache
        {
            JavaScriptConverterEngineResult Get(string cacheKey);
            void Set(string cacheKey, JavaScriptConverterEngineResult javaScriptConverters);
        }

    }
}